package otSpaces.server;

import java.io.Serializable;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.util.logging.Logger;

import otSpaces.OtSpaceFunctions;
import otSpaces.msg.Message;
import otSpaces.msg.Message.UnknownMessageTypeException;
import otSpaces.server.util.AmbiguousReference;
import otSpaces.util.IdTable;

import com.sun.sgs.app.AppContext;
import com.sun.sgs.app.Channel;
import com.sun.sgs.app.ChannelListener;
import com.sun.sgs.app.ClientSession;
import com.sun.sgs.app.Delivery;
import com.sun.sgs.app.ManagedObjectRemoval;

/**
 * A managed wrapper for ClientSession to provide the named methods through a
 * reliable channel with the expected TopicIdTable
 * 
 * 
 * 
 * If ClientSession is reclaimed, then the next call to "getClientSession" will
 * mark this object for removal
 * 
 * 
 * 
 * getClientSession() will return null when ClientSession has been removed
 * 
 * @author peter
 * 
 */
public class OtSpaceSubscriber implements ManagedObjectRemoval, Serializable,
		ChannelListener {

	public static final Logger LOG = Logger.getLogger(OtSpaceSubscriber.class
			.getName());

	private static final long serialVersionUID = -1995408027679788994L;

	public final AmbiguousReference<Channel> _channelRef;
	public final AmbiguousReference<ClientSession> _clientRef;
	public final IdTable _idTable = new IdTable();
	public final String _spaceName;

	public OtSpaceSubscriber(ClientSession clientSession, String spaceName) {

		_spaceName = spaceName;

		_clientRef = new AmbiguousReference<ClientSession>(clientSession);

		final BigInteger id = AppContext.getDataManager().getObjectId(
				clientSession);

		final Channel channel = AppContext.getChannelManager().createChannel(
				OtSpaceFunctions.topicSpaceNameCreate(id, spaceName), this,
				Delivery.RELIABLE);

		channel.join(clientSession);

		_channelRef = new AmbiguousReference<Channel>(channel);
	}

	/**
	 * Must return either the CLientSession or NULL (No throwing
	 * ObjectNotFOund!)
	 * 
	 * @return
	 */
	public ClientSession getClientSession() {
		final ClientSession clientSession = _clientRef.get();

		if (clientSession == null) {
			AppContext.getDataManager().removeObject(this);
		}

		return clientSession;
	}

	@Override
	public void receivedMessage(Channel channel, ClientSession sender,
			ByteBuffer data) {

		try {
			final Message message = Message.create(data);

			final short id = message._topicId;
			final String name = _idTable.contains(id) ? _idTable.get(id) : null;

			if (name != null
					&& OtSpaceFunctions.foldHashCode(name) == message._foldedTopicNameHashCode
							.byteValue()) {

				final OtSpaceTopic topic = (OtSpaceTopic) OtSpacesRegistry
						.locate(_spaceName).getChannel(name);

				if (topic != null) {
					topic._listener.get().receivedMessage(topic, sender,
							ByteBuffer.wrap(message._contents));
				} else {
					sendLeave(name);
				}
			}

		} catch (UnknownMessageTypeException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	@Override
	public void removingObject() {

		_channelRef.markForRemoval();
		_clientRef.markForRemoval();
	}

	public void sendJoin(String topicName) {
		if (null != getClientSession()) {

			final Message message = Message.createJoin(_idTable.set(topicName),
					topicName);

			_channelRef.get().send(message.toBuffer());
		}
	}

	public void sendLeave(String topicName) {

		// TODO : return T/F from here to the manager

		if (getClientSession() != null && _idTable.contains(topicName)) {

			final Message message = Message
					.createLeave(_idTable.get(topicName));

			_idTable.remove(topicName);

			_channelRef.get().send(message.toBuffer());

		} else {
			if (getClientSession() != null) {
				_idTable.remove(topicName);
			}
			LOG.severe("Client "
					+ (getClientSession() == null ? "missing" : "not sbscribed")
					+ " so they can't leave topic [" + topicName + "]");
		}
	}

	public void sendMessage(String topicName, ByteBuffer data) {

		// TODO : return T/F from here to the manager

		if (getClientSession() != null && _idTable.contains(topicName)) {

			final Message message = Message.createDownstream(
					_idTable.get(topicName), Message.toByteArray(data));

			_channelRef.get().send(message.toBuffer());
		} else {
			if (getClientSession() != null) {
				_idTable.remove(topicName);
			}
			LOG.severe("Client "
					+ (getClientSession() == null ? "missing" : "not sbscribed")
					+ " so they can't hear about topic [" + topicName + "]");
		}
	}
}
